import * as THREE from 'three'
import {
  addPointLineKeypoint,
  addPointsLineInstance,
  extractPointsLineInstance,
  addLine,
  removeLine,
  removePoint,
} from '../utils/AnnotationsLoader'
import { updateAnnotation } from '@/api/mark/markAnnotation'
import { SelectionBox } from '../SelectionBox.js'

const toolColor = 0xff00ff
const toolPointSize = 8

const _self = {
  scene: null,
  camera: null,
  controls: null,
  renderer: null,
  labelRenderer: null,
  track: null,
  mouse: {
    x: null,
    y: null,
  },
  annotations: {
    points: null,
  },
  insertPoint: null,
  updatePoint: null,
  tmpLinePoints: [],
  positionUpdatePoint: null,
  tmpLines: [],
  selectedLine: null,
  currentLineInstance: {
    mMarkAnnotationId: null,
  },
  startAnnotation: false,
}

const onMouseDown = (event) => {
  // 鼠标非左键点击，不做处理
  if (event.button !== 0) {
    return
  }

  if (event.altKey) {
    if (_self.positionUpdatePoint) {
      // 从车道线中删除点
      removePoint(_self.positionUpdatePoint, _self)
      if (_self.currentLineInstance.mMarkAnnotationId) {
        _self.currentLineInstance.needUpdate = true
      }
    }
    return
  }

  if (_self.insertPoint.visible === true) {
    const newPoint = addPointLineKeypoint(
      _self.scene,
      _self.track,
      _self.insertPoint.position,
      _self.insertPoint.pointsCloudIndex,
      'rgb(255, 0, 255)'
    )

    if (_self.selectedLine) {
      // 线段中间插入点
      _self.tmpLinePoints.splice(_self.selectedLine.point2.pointIndex, 0, newPoint)
      removeLine(_self.selectedLine, _self.tmpLines, _self.scene)
      const point1 = _self.selectedLine.point1
      const point2 = _self.selectedLine.point2
      newPoint.setPointIndex(point2.pointIndex, _self.scene)
      let p = point2
      while (p) {
        p.setPointIndex(p.pointIndex + 1, _self.scene)
        p = p.next
      }

      addLine(point1, newPoint, toolColor, _self.tmpLines, _self.scene)
      addLine(newPoint, point2, toolColor, _self.tmpLines, _self.scene)
    } else {
      if (event.shiftKey) {
        _self.tmpLinePoints.splice(0, 0, newPoint)
        newPoint.setPointIndex(0, _self.scene)
        if (_self.tmpLinePoints.length > 1) {
          let nextPointObject = _self.tmpLinePoints[1]
          addLine(newPoint, nextPointObject, toolColor, _self.tmpLines, _self.scene)
          while (nextPointObject) {
            nextPointObject.setPointIndex(nextPointObject.pointIndex + 1, _self.scene)
            nextPointObject = nextPointObject.next
          }
        }
      } else {
        _self.tmpLinePoints.push(newPoint)
        newPoint.setPointIndex(_self.tmpLinePoints.length - 1, _self.scene)
        if (_self.tmpLinePoints.length > 1) {
          const prePointObject = _self.tmpLinePoints[_self.tmpLinePoints.length - 2]
          addLine(prePointObject, newPoint, toolColor, _self.tmpLines, _self.scene)
        }
      }
    }

    if (_self.currentLineInstance.mMarkAnnotationId) {
      _self.currentLineInstance.needUpdate = true
    }
  } else if (!_self.updatePoint.visible) {
    if (_self.tmpLinePoints.length > 0) {
      return
    }
    if (_self.scene.pointsLines && _self.scene.pointsLines.length > 0) {
      const linePointsintersects = _self.raycaster.intersectObjects(_self.scene.pointsLines, true)
      if (linePointsintersects.length > 0) {
        const pointsLine = linePointsintersects[0].object
        _self.scene.remove(pointsLine.line)
        _self.scene.remove(pointsLine)
        _self.currentLineInstance = extractPointsLineInstance(
          pointsLine.annotationObject,
          'rgb(255, 0, 255)',
          _self.scene,
          _self.track
        )
        _self.tmpLinePoints.push(..._self.currentLineInstance.tmpLinePoints)
        _self.tmpLines.push(..._self.currentLineInstance.tmpLines)
        _self.startAnnotation = true
      }
    }
  }
}

const onMouseDrag = (event) => {
  if (event.button === 0 && _self.positionUpdatePoint) {
    // 车道线点位置调整
    // 禁用轨道控件，固定点云界面
    _self.controls.enabled = false
    const rect = _self.renderer.domElement.getBoundingClientRect()
    _self.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
    _self.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
    _self.raycaster.setFromCamera(_self.mouse, _self.camera)
    const intersects = _self.raycaster.intersectObjects([_self.scene.points], true)
    if (intersects.length > 0) {
      intersects.sort((a, b) => (a.distanceToRay < b.distanceToRay ? -1 : 1))
      const closer = intersects[0]
      const array = _self.scene.points.geometry.attributes.position.array
      _self.positionUpdatePoint.visible = false
      _self.positionUpdatePoint.position.copy({
        x: array[3 * closer.index],
        y: array[3 * closer.index + 1],
        z: array[3 * closer.index + 2],
      })
      _self.updatePoint.position.copy({
        x: array[3 * closer.index],
        y: array[3 * closer.index + 1],
        z: array[3 * closer.index + 2],
      })
      _self.positionUpdatePoint.pointsCloudIndex = closer.index
      if (_self.positionUpdatePoint.forwardLine) {
        const positions = _self.positionUpdatePoint.forwardLine.geometry.attributes.position.array
        positions[0] = _self.positionUpdatePoint.position.x
        positions[1] = _self.positionUpdatePoint.position.y
        positions[2] = _self.positionUpdatePoint.position.z
        positions[3] = _self.positionUpdatePoint.next.position.x
        positions[4] = _self.positionUpdatePoint.next.position.y
        positions[5] = _self.positionUpdatePoint.next.position.z
        _self.positionUpdatePoint.forwardLine.geometry.attributes.position.needsUpdate = true
      }
      if (_self.positionUpdatePoint.backLine) {
        const positions = _self.positionUpdatePoint.backLine.geometry.attributes.position.array
        positions[0] = _self.positionUpdatePoint.previous.position.x
        positions[1] = _self.positionUpdatePoint.previous.position.y
        positions[2] = _self.positionUpdatePoint.previous.position.z
        positions[3] = _self.positionUpdatePoint.position.x
        positions[4] = _self.positionUpdatePoint.position.y
        positions[5] = _self.positionUpdatePoint.position.z
        _self.positionUpdatePoint.backLine.geometry.attributes.position.needsUpdate = true
      }
      if (_self.currentLineInstance.mMarkAnnotationId) {
        _self.currentLineInstance.needUpdate = true
      }
    } else {
      _self.positionUpdatePoint = null
    }

    _self.renderer.render(_self.scene, _self.camera)
    _self.labelRenderer.render(_self.scene, _self.camera)
  }
}

const onMouseUp = (event) => {
  if (event.button === 0) {
    _self.controls.enabled = true
    if (_self.positionUpdatePoint) {
      _self.positionUpdatePoint.visible = true
    }
  }
}

const onMouseMove = (event) => {
  const rect = _self.renderer.domElement.getBoundingClientRect()
  _self.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  _self.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
  _self.raycaster.setFromCamera(_self.mouse, _self.camera)

  const lineintersects = _self.raycaster.intersectObjects(_self.tmpLines, true)
  if (lineintersects.length > 0) {
    _self.selectedLine = lineintersects[0].object
  } else {
    _self.selectedLine = null
  }

  let updateCloser
  const linePointsintersects = _self.raycaster.intersectObjects(_self.tmpLinePoints, true)
  if (linePointsintersects.length > 0) {
    linePointsintersects.sort((a, b) => (a.distanceToRay < b.distanceToRay ? -1 : 1))
    updateCloser = linePointsintersects[0]
  }

  if (_self.positionUpdatePoint) {
    _self.positionUpdatePoint.visible = true
  }
  _self.positionUpdatePoint = null
  _self.insertPoint.visible = false
  _self.updatePoint.visible = false

  let insertCloser
  if (_self.startAnnotation) {
    const normalPointsintersects = _self.raycaster.intersectObjects([_self.scene.points], true)
    if (normalPointsintersects.length > 0) {
      normalPointsintersects.sort((a, b) => (a.distanceToRay < b.distanceToRay ? -1 : 1))
      insertCloser = normalPointsintersects[0]
    }
  }

  const array = _self.scene.points.geometry.attributes.position.array
  if (insertCloser && updateCloser) {
    if (updateCloser.distanceToRay <= insertCloser.distanceToRay) {
      _self.updatePoint.position.copy({
        x: updateCloser.point.x,
        y: updateCloser.point.y,
        z: updateCloser.point.z,
      })
      _self.positionUpdatePoint = updateCloser.object
      _self.positionUpdatePoint.visible = false
      _self.updatePoint.visible = true
    } else {
      _self.insertPoint.position.copy({
        x: array[3 * insertCloser.index],
        y: array[3 * insertCloser.index + 1],
        z: array[3 * insertCloser.index + 2],
      })
      _self.insertPoint.pointsCloudIndex = insertCloser.index
      _self.insertPoint.visible = true
    }
  } else if (updateCloser) {
    _self.updatePoint.position.copy({
      x: updateCloser.point.x,
      y: updateCloser.point.y,
      z: updateCloser.point.z,
    })
    _self.positionUpdatePoint = updateCloser.object
    _self.positionUpdatePoint.visible = false
    _self.updatePoint.visible = true
  } else if (insertCloser) {
    _self.insertPoint.position.copy({
      x: array[3 * insertCloser.index],
      y: array[3 * insertCloser.index + 1],
      z: array[3 * insertCloser.index + 2],
    })
    _self.insertPoint.pointsCloudIndex = insertCloser.index
    _self.insertPoint.visible = true
  }

  _self.renderer.render(_self.scene, _self.camera)
  _self.labelRenderer.render(_self.scene, _self.camera)
}

const onKeyDown = (event) => {
  // 空格键，开始点云车道线的标注
  if (event.keyCode === 32) {
    if (_self.tmpLinePoints.length > 0) {
    }
    _self.startAnnotation = !_self.startAnnotation
    if (!_self.startAnnotation) {
      _self.insertPoint.visible = false
    }
    event.returnValue = false
    return
  }

  if (event.ctrlKey && event.key.toLowerCase() === 's') {
    event.preventDefault()
    if (_self.tmpLinePoints.length > 0) {
      const pointIndexs = []
      _self.tmpLinePoints.forEach((e) => {
        pointIndexs.push(e.pointsCloudIndex)
      })
      if (_self.currentLineInstance.mMarkAnnotationId && _self.currentLineInstance.needUpdate) {
        let params = {
          mMarkAnnotationId: _self.currentLineInstance.mMarkAnnotationId,
          x: pointIndexs.toString(),
          z: 'split',
        }
        updateAnnotation(params).then((response) => {
          if (response.code === 200) {
            _self.currentLineInstance.annotationObject.threeD.bndbox.x = pointIndexs.toString()
            const annotationObject = _self.currentLineInstance.annotationObject
            addPointsLineInstance(annotationObject, annotationObject.fillStyle, _self.scene, _self.track, 8)
            tool.reset(_self)
            _self.renderer.render(_self.scene, _self.camera)
            _self.labelRenderer.render(_self.scene, _self.camera)
          }
        })
      } else {
        tool.addNew(pointIndexs, null, 'pointLine', (e) => {
          tool.reset(_self)
        })
      }
      event.returnValue = false
    }
    return
  }

  if (event.ctrlKey && event.key.toLowerCase() === 'z') {
    event.preventDefault()
    const delPoint = _self.tmpLinePoints[_self.tmpLinePoints.length - 1]
    removePoint(delPoint, _self)
    event.returnValue = false
  }

  if (
    event.key.toLowerCase() === 'w' ||
    event.key.toLowerCase() === 's' ||
    event.key.toLowerCase() === 'a' ||
    event.key.toLowerCase() === 'd'
  ) {
    event.returnValue = false
    return
  }
}

const onToggleIn = (params) => {
  _self.scene = params.scene
  _self.camera = params.camera
  _self.controls = params.controls
  _self.renderer = params.renderer
  _self.labelRenderer = params.labelRenderer
  _self.track = params.track

  _self.raycaster = new THREE.Raycaster()
  _self.raycaster.params.Points.threshold = 0.1
  _self.raycaster.linePrecision = 0.1
  _self.raycaster.INTERSECTED = null

  _self.controls.keys = {
    LEFT: 65,
    UP: 87,
    RIGHT: 68,
    BOTTOM: 83,
  }
  _self.controls.enableKeys = true

  // 车道线点云插入工具
  const geometry1 = _self.track(new THREE.BufferGeometry())
  const canvas1 = document.createElement('canvas')
  canvas1.width = 100
  canvas1.height = 100
  const context1 = canvas1.getContext('2d')
  context1.fillStyle = '#ff00ff'
  context1.arc(50, 50, 45, 0, 2 * Math.PI)
  context1.fill()
  const texture1 = _self.track(new THREE.Texture(canvas1))
  texture1.needsUpdate = true
  const material1 = _self.track(
    new THREE.PointsMaterial({ size: toolPointSize, vertexColors: THREE.VertexColors, depthTest: false, map: texture1 })
  )
  material1.sizeAttenuation = false
  geometry1.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3))
  geometry1.setAttribute('color', new THREE.Float32BufferAttribute([1, 0, 1], 3))

  _self.insertPoint = _self.track(new THREE.Points(geometry1, material1))
  _self.insertPoint.visible = false
  _self.insertPoint.name = 'insertPoint'
  _self.scene.add(_self.insertPoint)

  // 车道线点云修改工具
  const geometry2 = _self.track(new THREE.BufferGeometry())
  const canvas2 = document.createElement('canvas')
  canvas2.width = 100
  canvas2.height = 100
  const context2 = canvas2.getContext('2d')
  context2.fillStyle = '#ffff00'
  context2.arc(50, 50, 45, 0, 2 * Math.PI)
  context2.fill()
  const texture2 = _self.track(new THREE.Texture(canvas2))
  texture2.needsUpdate = true
  const material2 = _self.track(
    new THREE.PointsMaterial({ size: toolPointSize, vertexColors: THREE.VertexColors, depthTest: false, map: texture2 })
  )
  material2.sizeAttenuation = false
  geometry2.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3))
  geometry2.setAttribute('color', new THREE.Float32BufferAttribute([1, 1, 0], 3))

  _self.updatePoint = _self.track(new THREE.Points(geometry2, material2))
  _self.updatePoint.visible = false
  _self.updatePoint.name = 'updatePoint'
  _self.scene.add(_self.updatePoint)

  window.addEventListener('keydown', onKeyDown, true)

  _self.selectionBox = new SelectionBox(_self.camera, _self.scene)
  _self.selectionBox.labelTool = 'pointLine'

  if (_self.scene.pointsLines) {
    _self.scene.pointsLines.forEach((pointsLine) => {
      pointsLine.material.size = 8
    })
  }
  _self.renderer.render(_self.scene, _self.camera)
}

const onToggleOut = () => {
  _self.scene.remove(_self.insertPoint)
  _self.scene.remove(_self.updatePoint)
  window.removeEventListener('keydown', onKeyDown)
}

const reset = () => {
  _self.tmpLinePoints.forEach((p) => {
    if (p.label) {
      _self.scene.remove(p.label)
    }
    _self.scene.remove(p)
  })
  _self.tmpLinePoints.splice(0, _self.tmpLinePoints.length)
  _self.tmpLines.forEach((l) => {
    _self.scene.remove(l)
  })
  _self.tmpLines.splice(0, _self.tmpLines.length)
  _self.currentLineInstance = {
    mMarkAnnotationId: null,
  }
}

const controlsChange = (e) => {
  if (_self.tmpLinePoints.length > 0) {
    _self.selectionBox.startPoint.set(-1, 1, 0.5)
    _self.selectionBox.endPoint.set(1, -1, 0.5)

    const visibleIndices = []
    const selected = _self.selectionBox.select()
    visibleIndices.push(...selected.pointsIndex)

    if (visibleIndices.length > 0) {
      let i = visibleIndices[0]
      const array = _self.scene.points.geometry.attributes.position.array
      let minX = array[i * 3],
        minY = array[i * 3 + 1],
        minZ = array[i * 3 + 2],
        maxX = array[i * 3],
        maxY = array[i * 3 + 1],
        maxZ = array[i * 3 + 2]
      visibleIndices.forEach((i) => {
        minX = Math.min(minX, array[i * 3])
        minY = Math.min(minY, array[i * 3 + 1])
        minZ = Math.min(minZ, array[i * 3 + 2])
        maxX = Math.max(maxX, array[i * 3])
        maxY = Math.max(maxY, array[i * 3 + 1])
        maxZ = Math.max(maxZ, array[i * 3 + 2])
      })

      let x = maxX - minX,
        y = maxY - minY,
        z = maxZ - minZ
      _self.raycaster = new THREE.Raycaster()
      const threshold = Math.cbrt((x * y * z) / visibleIndices.length) / 3
      _self.raycaster.params.Points.threshold = threshold
      _self.raycaster.params.Line.threshold = threshold
    }
  }
}

export const tool = {
  name: 'pointLine',
  onMouseDown: onMouseDown,
  onMouseDrag: onMouseDrag,
  onMouseUp: onMouseUp,
  onMouseMove: onMouseMove,
  onToggleIn: onToggleIn,
  onToggleOut: onToggleOut,
  reset: reset,
  controlsChange: controlsChange,
}
